package pira.db.ui;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.Index;
import org.neo4j.graphdb.index.IndexHits;
import org.neo4j.graphdb.index.IndexManager;
import org.neo4j.kernel.EmbeddedGraphDatabase;

import pira.constants.PIRAConfig;
import pira.constants.PIRAConfig.MyRelationshipTypes;
import pira.elements.Author;
import pira.elements.Paper;
import pira.parser.xml.dblp.XMLFilesReader;
import pira.utils.Log;

public class FullDBUI {

	public static final String DBPATH = "fullDB_v5";
	
	// constant for index
	public static final String PAPERS = "papers";
	public static final String AUTHORS = "authors";
	public static final String NOT_EXIST = "Not Exist!";
	public static final boolean D = false;

	// transaction for database
	private Transaction tx;

	// graph database service
	private GraphDatabaseService graphDb;

	public GraphDatabaseService getGraphDatabaseService() {
		return graphDb;
	}

	// database index
	private IndexManager index;

	// index for papers
	public Index<Node> papers;

	// index for authors
	public Index<Node> authors;

	public FullDBUI() {
		Map<String, String> configuration = EmbeddedGraphDatabase
				.loadConfigurations(PIRAConfig.CONFIG);
		graphDb = new EmbeddedGraphDatabase(DBPATH, configuration);
		tx = graphDb.beginTx();
		try {
			index = graphDb.index();

			this.papers = index.forNodes(PAPERS);
			this.authors = index.forNodes(AUTHORS);

			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public FullDBUI(String path) {
		Map<String, String> configuration = EmbeddedGraphDatabase
				.loadConfigurations(PIRAConfig.CONFIG);
		graphDb = new EmbeddedGraphDatabase(path, configuration);
		tx = graphDb.beginTx();
		try {
			index = graphDb.index();

			this.papers = index.forNodes(PAPERS);
			this.authors = index.forNodes(AUTHORS);

			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public void modifyNode() {
		tx = graphDb.beginTx();
		try {
			Node node = graphDb.getNodeById(0);
			authors.remove(node);
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public void shutDown() {
		System.out.println("Program ends ...");
		graphDb.shutdown();
	}
	
	public void verifyTitle() {
		long count = 0;
		for (Node paper : papers.query(Paper.TITLE, "*")) {
			count++;

//			System.out.println(paper.getProperty(Paper.PAPERID));
//			System.out.println(paper.getProperty(Paper.TITLE));
			if (!paper.hasProperty(Paper.TITLE)) {
				System.out.println(paper.getId() + " has no title.");
			}
			
//			if (!paper.hasProperty(Paper.CLUSTERID)) {
//				System.out.println(paper.getId() + " has no clusterid.");
//			}
			
//			if (!paper.hasRelationship()) {
//				System.out.println(paper.getId() + " has no relationships.");
//			}
			
			if (count % 10000 == 0) {
				System.out.println(count);
			}
		}
		System.out.println("Totally : " + count);
	}	
	
	public void findPaperByTitle(String title) {
		IndexHits<Node> hits = papers.get(Paper.TITLE, normal(title));
		if (hits.size() != 0) {
			System.out.println("found");
			System.out.println(hits.size());
			System.out.println("id : " + ((Node) hits.getSingle()).getId());
		} else {
			System.out.println("not found");
		}
		
		int count = 0;
		for (Node node : papers.query(Paper.PAPERID, "*")) {
			count++;
			if (count < 10)
				System.out.println(node.getId());
//			System.out.println(node.getProperty(Paper.TITLE));
		}
	}
	
	public void findPaperByCludterid(String id) {
		IndexHits<Node> hits = papers.get(Paper.CLUSTERID, id);
		if (hits.size() == 1) {
			System.out.println("found");
			System.out.println(hits.size());
			System.out.println("id : " + ((Node) hits.getSingle()).getId());
			System.out.println(hits.getSingle().getProperty(Paper.TITLE));
		} else if (hits.size() > 1) {
			System.out.println("found");
			for (Node node : hits) {
				System.out.println(node.getId() + "\t" + node.getProperty(Paper.TITLE));
			}
		} else {
			System.out.println("Not find.");
		}
	}
	
	public void findPaperByPaperID(String paperid) {
		IndexHits<Node> hits = papers.get(Paper.PAPERID, paperid);
		if (hits.size() != 0) {
			System.out.println("found.");
			System.out.println(hits.size());
			Node node = hits.getSingle();
			System.out.println(node);
			System.out.println(hits.getSingle().getId() + "\t" + hits.getSingle().getProperty(Paper.TITLE));
		} else 
			System.out.println("not found.");
	}
	
	public void cleanupRedundancy() {
		long startTime = System.currentTimeMillis();
		tx = graphDb.beginTx();
		long paperCount = 0;
		try {
			for (Node paper : papers.query(Paper.PAPERID, "*")) {
				paperCount++;
				Iterable<org.neo4j.graphdb.Relationship> cites = paper.getRelationships(MyRelationshipTypes.CITE, Direction.OUTGOING);
				HashSet<Node> uniqueCites = new HashSet<Node>();
				for (org.neo4j.graphdb.Relationship r : cites) {
					Node endNode = r.getEndNode();
					r.delete();
					if (!uniqueCites.contains(endNode)) {
						uniqueCites.add(endNode);
					}
				}
				
				for (Node node : uniqueCites) {
					paper.createRelationshipTo(node, MyRelationshipTypes.CITE);
				}
				
				if (paperCount % 10000 == 0) {
					long timeElapsed = (System.currentTimeMillis() - startTime) / 1000;
					System.out.println(paperCount + "\t" + timeElapsed + "s\t" + (float)(paperCount * 100 / 1388883) + "%.");
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public void findNodeByID(long id) {
		Node node = graphDb.getNodeById(id);
		System.out.println("paper id : " + node.getProperty(Paper.PAPERID));
	}
	
	public void veriftNewID() {
		int count = 0;
		for (Node paper : papers.query(Paper.PAPERID, "*")) {
			long newId = (Long) paper.getProperty("new id");
			if (newId == -1) {
				System.out.println(paper.getId() + "\t" + paper.getProperty(Paper.TITLE));
				count++;
			}
		}
		System.out.println("count = " + count);
	}
	
	/*
	 * This method calculate the number of citation and the number of publication
	 * 		for a author and update information to database
	 * Log files used: Citation_number_for_authors.txt
	 */
	public void calculateCitaNumAndPubliNumOfAuthor() {
//		Log log = new Log("outputs/FullDB_Author_Citation.txt");
		tx = graphDb.beginTx();
		try {
			// record how many authors has been traversed
			long authorID = 0;
			int authorCount = 0;
			while (authorID <= 4017264) {
				Node author = graphDb.getNodeById(authorID);
				if (author.hasProperty(Author.FULLNAME)) {
					authorCount++;

					int citationNumber = 0;
					int publicationNumber = 0;
					
					Iterator<Relationship> wrs = author.getRelationships(
							MyRelationshipTypes.WROTE,
							Direction.OUTGOING).iterator();

					while (wrs.hasNext()) {
						
						publicationNumber++;
						// get the paper
						Node p = wrs.next().getEndNode();
						citationNumber += (Integer) p.getProperty(Paper.CITEDTIMES);

					}

					author.setProperty(Author.CITATIONNUMBER, citationNumber);
					author.setProperty(Author.PUBLICATIONNUMBER, publicationNumber);
				}
				authorID++;
				
				if (authorCount % 10000 == 0) {
					System.out.println(authorCount);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			tx.success();
		} finally {
//			log.close();
			tx.finish();
		}
	}
	
	/*
	 * Count the cited times for papers
	 */
	public void calculateCitedTimesOfPapers() {
		tx = graphDb.beginTx();
		try {
			long paperCount = 0;
			for (Node paper : papers.query(Paper.PAPERID, "*")) {
				paperCount++;
				int citedTimes= 0;
				for (@SuppressWarnings("unused") Relationship r : paper.getRelationships(MyRelationshipTypes.CITE, Direction.INCOMING))
					citedTimes++;
				paper.setProperty(Paper.CITEDTIMES, citedTimes);
				if (paperCount % 10000 == 0) {
					System.out.println(paperCount);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	public void countNodeNumber() {
		Iterable<Node> all = graphDb.getAllNodes();
		long paperNb = 0;
		long authorNb = 0;
		for (Node node : all) {
			if (node.getId() == 0 || node.getId() == 1 || node.getId() == 2)
				continue;
			if (node.hasProperty(Paper.TITLE))
				paperNb++;
			else if (node.hasProperty(Author.FULLNAME))
				authorNb++;
		}
		System.out.println("Paper nb : " + paperNb);
		System.out.println("Author nb : " + authorNb);
		
		tx = graphDb.beginTx();
		try {
			Node global = graphDb.getNodeById(PIRAConfig.GLOBALNODEID);
			global.setProperty(PIRAConfig.PAPERNUMBER, paperNb);
			global.setProperty(PIRAConfig.AUTHORNUMBER, authorNb);
			global.setProperty(PIRAConfig.ALLNODENUMBER, paperNb + authorNb + 2);
			tx.success();
		} finally {
			tx.finish();
		}
	}
	
	// method used to calculate the h-index value
	public void calculHIndex() {
		long startTime = System.currentTimeMillis();
		Log hLog = new Log("outputs/FullDB_Infos_Of_Author.txt");
		String str = "Order\tAuthorID\tAuthorName\tCitation Number\t"
				+ "Publication Number\tH-Index\n";
		hLog.write(str);
		// System.out.print(str);

		long authorID = 0;
		tx = graphDb.beginTx();
		try {
			int authorCount = 0;

			while (authorID <= 4017264) {
				Node author = graphDb.getNodeById(authorID);
				if (author.hasProperty(Author.FULLNAME)) {
					authorCount++;
					str = authorCount + "\t" + authorID + "\t"
							+ author.getProperty(Author.FULLNAME) + "\t";
					// System.out.print(str);
					hLog.write(str);

					calculateAuthorHIndex(author, authorID, hLog);
				}
				authorID++;

				if (authorCount % 10000 == 0) {
					System.out.println(authorCount);
					tx.success();
					tx.finish();
					tx = graphDb.beginTx();
				}
			}
			str = "In total, there're " + authorCount + " authors.\n";
			System.out.print(str);

			// hLog.write(str);
			tx.success();
		} finally {
			hLog.close();
			tx.finish();
		}

		long timePassed = System.currentTimeMillis() - startTime;
		System.out.println("Run " + timePassed / 1000 + " seconds.");
	}

	private void calculateAuthorHIndex(Node author, long authorID, Log hLog) {

		// algorithm for calculate H-Index
		String str = null;

		int publicationNumber = (Integer) author
				.getProperty(Author.PUBLICATIONNUMBER);
		int i = 1;
		for (i = 1; i <= publicationNumber; i++) {
			Iterator<Relationship> rels = author.getRelationships(
					MyRelationshipTypes.WROTE).iterator();
			int count = 0;
			while (rels.hasNext()) {
				Node paper = rels.next().getEndNode();

				int size = 0;

				Iterator<Relationship> myCites = paper.getRelationships(
						MyRelationshipTypes.CITE, Direction.INCOMING)
						.iterator();

				while (myCites.hasNext()) {
					myCites.next();
					size++;
				}

				if (size >= i)
					count++;
				if (count >= i)
					break;
			}

			if (count < i)
				break;
		}

		author.setProperty(Author.HINDEX, (i - 1));

		str = author.getProperty(Author.CITATIONNUMBER) + "\t"
				+ author.getProperty(Author.PUBLICATIONNUMBER) + "\t"
				+ author.getProperty(Author.HINDEX) + "\n";
		// System.out.print(str);
		hLog.write(str);
	}
	
	public static void main(String[] args) {
		FullDBUI fdbui = new FullDBUI();
//		fdbui.calculateCitedTimesOfPapers();
//		fdbui.calculateCitaNumAndPubliNumOfAuthor();
//		fdbui.calculHIndex();
//		fdbui.countNodeNumber();
//		fdbui.modifyNode();
//		fdbui.outputHIndex();
		fdbui.testSort();
		fdbui.shutDown();
	}
	
	public void testSort() {
		long[] rank = new long[2952717];
		int i = 0;
		for (Node node : authors.query(Author.AUTHORID, "*")) {
			rank[i] = node.getId();
			i++;
		}
		System.out.println("i = " + i);
		long startTime = System.currentTimeMillis();
		hIndexQuickSort(rank, 0, 2952716);
		long timeElapsed = System.currentTimeMillis() - startTime;
		System.out.println("Sort completed after " + timeElapsed/1000 + "s.");
		
		Log sort = new Log("hindex_rank.txt");
		i = 1;
		for (long r : rank) {
			String str = i + "\t" + r + "\t" + graphDb.getNodeById(r).getProperty(Author.HINDEX) + "\n";
			sort.write(str);
			i++;
		}
	}
	
	
	public void hIndexQuickSort(long[] arr, int left, int right) {
		int i = left, j = right;
		long tmp = -1;
		if (arr[(left+right)/2] == 0) {
			System.out.println(left);
			System.out.println(right);
		}
		int pivot = (Integer) graphDb.getNodeById(arr[(left + right)/2]).getProperty(Author.HINDEX);
		
		while (i <= j) {
			while (((Integer)graphDb.getNodeById(arr[i]).getProperty(Author.HINDEX)) > pivot)
				i++;
			while (((Integer)graphDb.getNodeById(arr[j]).getProperty(Author.HINDEX)) < pivot)
				j--;
			if (i <= j) {
				tmp = arr[i];
				arr[i] = arr[j];
				arr[j] = tmp;
				i++;
				j--;
			}
		}
		
		if (left < j)
			hIndexQuickSort(arr, left, j);
		if (i < right)
			hIndexQuickSort(arr, i, right);
	}
	
	/*
	 *  normal a string, replace all the no-letter to space
	 *  delete the continuous 2 or more space
	 *  change them to lower case
	 */
	
	public static String normal(String src) {
		String normal = null;
		Pattern p = Pattern.compile("\\W");
		Matcher m = p.matcher(src);
		normal = m.replaceAll(" ");
		p = Pattern.compile(" {2,}");
		m = p.matcher(normal);
		normal = m.replaceAll(" ");
		normal = normal.toLowerCase().trim();
		return normal;
	}
	
	public void outputHIndex() {
		Log hindex = new Log("hindex.txt");
		long i = 0;
		for (Node author : authors.query(Author.AUTHORID, "*")) {
			i++;
			hindex.write(i + "\t" + author.getId() + "\t" + author.getProperty(Author.HINDEX) + "\n");
		}
		hindex.close();
	}
}
